﻿/*************************************
 * Creator:SW-PeiYang
 * DateTime:2023/2/20 14:06:49
 * Description:<Description>
 * CopyRight:中广核研究院有限公司
 * ***********************************/

using Cpjit.SunnyPaine.Toolkit.Net.Sockets.IDataHandlingApdapter;
using Cpjit.SunnyPaine.Toolkit.Net.Sockets.Model;
using Cpjit.SunnyPaine.Toolkit.Net.Sockets.Util;
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Cpjit.SunnyPaine.Toolkit.Net.Sockets.TCP.Impl
{
    /// <summary>
    /// 表示TCP客户端对象。
    /// </summary>
    public class TcpClient : ITcpClient
    {
        #region 私有变量
        private bool isRunning = false;
        /// <summary>
        /// 是否正在重连。
        /// </summary>
        private volatile bool isReconnecting = false;
        private int bufferSize = ushort.MaxValue;
        private Encoding encoder = Encoding.UTF8;
        #endregion


        #region 公共属性
        /// <summary>
        /// TCP客户端实例。
        /// </summary>
        public Socket Client { get; private set; }
        /// <summary>
        /// 客户端Id。
        /// </summary>
        public string ClientId { get; }
        /// <summary>
        /// 服务端IP。
        /// </summary>
        public IPAddress ServerIP { get; }
        /// <summary>
        /// 服务端端口。
        /// </summary>
        public int ServerPort { get; }
        /// <summary>
        /// 服务端终结点。
        /// </summary>
        public EndPoint ServerEndPoint { get; }
        /// <summary>
        /// 客户端是否已连接服务端。
        /// </summary>
        public bool IsConnected { get; private set; }
        /// <summary>
        /// 文本编码。
        /// </summary>
        public Encoding Encoder
        {
            get { return this.encoder; }
            set
            {
                this.encoder = value;
                ByteBlock.Encoder = value;
            }
        }
        /// <summary>
        /// 是否启用心跳。
        /// </summary>
        public bool IsUseHeartbeat { get; set; }
        /// <summary>
        /// 心跳周期。
        /// </summary>
        public uint HeartbeatInterval { get; set; } = 3000;
        /// <summary>
        /// 是否启用重连。
        /// </summary>
        public bool IsAutoReconnect { get; set; }
        /// <summary>
        /// 数据处理适配器。
        /// </summary>
        public IDataHandlingAdapter Adapter { get; private set; }
        #endregion


        #region 事件委托
        /// <summary>
        /// 当收到消息时发生。
        /// </summary>
        public event Action<ITcpClient, SessionEventArgs> OnReceived;
        /// <summary>
        /// 当有客户端连接时发生。
        /// </summary>
        public event Action<ITcpClient, SessionEventArgs> OnConnected;
        /// <summary>
        /// 当有客户端断开连接时发生。
        /// </summary>
        public event Action<ITcpClient, SessionEventArgs> OnDisConnected;
        /// <summary>
        /// 当出现错误时发生。
        /// </summary>
        public event Action<ITcpClient, SessionEventArgs, Exception> OnError;
        #endregion


        #region 构造方法
        /// <summary>
        /// 使用指定的服务端IP和端口创建<see cref="TcpClient"/>的实例。
        /// </summary>
        /// <param name="serverIP"></param>
        /// <param name="serverPort"></param>
        public TcpClient(string serverIP, int serverPort) : this(serverIP, serverPort, null)
        { }

        /// <summary>
        /// 使用指定的服务端IP和端口创建<see cref="TcpClient"/>的实例。
        /// </summary>
        /// <param name="serverIP"></param>
        /// <param name="serverPort"></param>
        /// <param name="clientId">客户端Id。</param>
        public TcpClient(string serverIP, int serverPort, string clientId)
        {
            IPAddress ip;
            if (!IPAddress.TryParse(serverIP, out ip))
            {
                throw new ArgumentException("IP地址不合法。");
            }
            if (serverPort < 0 || serverPort > 65535)
            {
                throw new ArgumentException("端口不合法，端口号范围应为0~65536");
            }

            this.ServerIP = ip;
            this.ServerPort = serverPort;
            this.ServerEndPoint = new IPEndPoint(this.ServerIP, this.ServerPort);
            if (string.IsNullOrEmpty(clientId))
            {
                clientId = $"{serverIP}:{serverPort}";
            }
            this.ClientId = clientId;
        }
        #endregion


        #region 私有方法
        /// <summary>
        /// 连接到服务端的回调方法。
        /// </summary>
        /// <param name="iar"></param>
        private void ConnectCallback(IAsyncResult iar)
        {
            Socket socket = iar.AsyncState as Socket;
            socket.EndConnect(iar);

            try
            {
                this.IsConnected = true;
                this.isRunning = true;
                if (this.IsUseHeartbeat && this.HeartbeatInterval > 0)
                {
                    this.Heartbeat();
                }

                this.OnConnected?.Invoke(this, new SessionEventArgs());

                //开始接收数据
                SocketSession session = new SocketSession(socket, this.bufferSize);
                socket.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                    new AsyncCallback(this.ReceiveDataCallback), session);
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs(), ex);
                //开始接收数据
                SocketSession session = new SocketSession(socket, this.bufferSize);
                socket.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                    new AsyncCallback(this.ReceiveDataCallback), session);
            }
        }

        /// <summary>
        /// 接收数据的回调方法。
        /// </summary>
        /// <param name="iar"></param>
        private void ReceiveDataCallback(IAsyncResult iar)
        {
            if (!this.isRunning)
            {
                return;
            }

            SocketSession session = iar.AsyncState as SocketSession;
            Socket client = session.Client;
            EndPoint endPoint = new IPEndPoint(IPAddress.Parse(session.ClientIP), session.ClientPort);

            try
            {
                if (this.IsConnected == false)
                {
                    this.OnDisConnected?.Invoke(this, new SessionEventArgs());
                    this.DisConnect();
                    this.ReConnect();
                    return;
                }

                int receiveCount = client.EndReceive(iar);
                if (receiveCount == 0)
                {
                    this.OnDisConnected?.Invoke(this, new SessionEventArgs());
                    this.IsConnected = false;
                    this.DisConnect();
                    this.ReConnect();
                }
                else
                {
                    session.ReceivedCount = receiveCount;
                    if (this.Adapter != null)
                    {
                        this.Adapter.PreviewReceived(session);
                    }
                    else
                    {
                        byte[] data = new byte[receiveCount];
                        Array.Copy(session.Buffer, 0, data, 0, receiveCount);
                        this.OnReceived?.Invoke(this, new SessionEventArgs()
                        {
                            RemoteEndPoint = endPoint,
                            Data = data
                        });
                    }

                    //继续接收来自客户端的消息
                    client.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                        new AsyncCallback(this.ReceiveDataCallback), session);
                }
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode != SocketError.Success)
                {
                    this.OnDisConnected?.Invoke(this, new SessionEventArgs());
                    this.IsConnected = false;
                    this.DisConnect();
                    this.ReConnect();
                }
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs()
                {
                    RemoteEndPoint = endPoint,
                }, new Exception($"处理从客户端{session.IPAddress}接收的消息出错。", ex));
                //继续接收来自客户端的消息
                client.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                    new AsyncCallback(this.ReceiveDataCallback), session);
            }
        }

        /// <summary>
        /// 发送消息的回调方法。
        /// </summary>
        /// <param name="iar"></param>
        private void SendCallback(IAsyncResult iar)
        {
            try
            {
                Socket client = iar.AsyncState as Socket;
                if (client.Connected)
                {
                    client.EndSend(iar);
                }
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs(), ex);
            }
        }

        private void Heartbeat()
        {
            Ping ping = new Ping();
            ByteBlock block = new ByteBlock();
            block.WriteValue((byte)1);
            block.WriteValue(this.ClientId);
            block.EndWrite();

            byte[] data = block.ToAarray();
            Task.Factory.StartNew(() =>
            {
                while (this.isRunning)
                {
                    try
                    {
                        PingReply reply = ping.Send(this.ServerIP, 100);
                        if (reply.Status != IPStatus.Success && this.Client.Connected)
                        {
                            this.Client.Close(200);
                        }
                        else
                        {
                            this.Send(data);
                        }
                    }
                    catch (Exception ex)
                    {
                        this.OnError?.Invoke(this, new SessionEventArgs(), ex);
                    }
                    Thread.Sleep(1000);
                }
                ping.Dispose();
            });
        }

        private bool ReConnect()
        {
            if (this.IsAutoReconnect && this.isReconnecting == false && this.IsConnected == false)
            {
                this.isReconnecting = true;
                Task<bool> task = Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000);
                    int retry = 1;
                    while (this.isRunning)
                    {
                        if (this.Connect())
                        {
                            this.isReconnecting = false;
                            return true;
                        }
                        this.OnError?.Invoke(this, new SessionEventArgs()
                        {
                            RemoteEndPoint = this.ServerEndPoint,
                            SysMsg = $"正在重连，第{retry++}次重试失败。"
                        },
                        new SocketException((int)SocketError.NotConnected));
                        Thread.Sleep(2000);
                    }
                    return false;
                });
                task.Wait();
                return task.Result;
            }
            return false;
        }
        #endregion


        #region 公共方法，ISocket成员
        /// <summary>
        /// 设置数据处理器适配器。
        /// </summary>
        /// <param name="adapter"></param>
        public void SetDataHandlingAdapter(IDataHandlingAdapter adapter)
        {
            if (this.Adapter == null)
            {
                throw new ArgumentNullException("adapter");
            }
            this.Adapter = adapter;
            this.bufferSize = this.Adapter.Capacity + 4;//4表示指示有效数据长度的包头
            this.Adapter.GoReceived += (session, data) =>
            {
                this.OnReceived?.Invoke(this, new SessionEventArgs()
                {
                    RemoteEndPoint = new IPEndPoint(IPAddress.Parse(session.ClientIP), session.ClientPort),
                    Data = data
                });
            };
            this.Adapter.GoSend += (client, data, isAsync) =>
            {
                if (isAsync)
                {
                    client.BeginSend(data, 0, data.Length, SocketFlags.None,
                        new AsyncCallback(this.SendCallback), client);
                }
                else
                {
                    client.Send(data, 0, data.Length, SocketFlags.None);
                }
            };
        }
        #endregion

        #region 公共方法，ITcpClient成员
        /// <summary>
        /// 连接服务端。
        /// </summary>
        /// <returns></returns>
        public bool Connect()
        {
            try
            {
                if (this.IsConnected == false)
                {
                    if (this.Client == null)
                    {
                        this.Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    }
                    this.Client.Connect(this.ServerEndPoint);
                    this.isRunning = true;
                    this.IsConnected = true;
                    this.OnConnected?.Invoke(this, new SessionEventArgs());

                    //开始接收数据
                    SocketSession session = new SocketSession(this.Client, this.bufferSize);
                    this.Client.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                        new AsyncCallback(this.ReceiveDataCallback), session);
                }
                return true;
            }
            catch (Exception ex)
            {
                if (!this.isReconnecting)
                {
                    this.OnError?.Invoke(this, new SessionEventArgs(), ex);
                }
                return false;
            }
        }

        /// <summary>
        /// 异步连接服务端。
        /// </summary>
        public void ConnectAsync()
        {
            try
            {
                if (this.IsConnected == false)
                {
                    this.Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    this.Client.BeginConnect(this.ServerEndPoint, new AsyncCallback(this.ConnectCallback), this.Client);
                }
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs(), ex);
            }
        }

        /// <summary>
        /// 断开与服务端的连接，并释放套接字。
        /// 注意：当启用<see cref="IsAutoReconnect"/>时，调用该方法会立即触发重连。
        /// </summary>
        public void DisConnect()
        {
            try
            {
                if (this.Client != null)
                {
                    this.IsConnected = false;
                    this.Client.Close(100);
                    this.Client.Dispose();
                    this.Client = null;
                }
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs(), ex);
            }
            finally
            {
                this.Client = null;
                this.OnDisConnected?.Invoke(this, new SessionEventArgs());
            }
        }

        /// <summary>
        /// 断开与服务端的连接，并释放所有资源（安全的，不会抛出异常）。
        /// </summary>
        public void Close()
        {
            try
            {
                if (this.Client != null)
                {
                    this.isRunning = false;
                    this.IsConnected = false;
                    this.Adapter?.Dispose();
                    this.Client.Close(100);
                    this.Client.Dispose();
                }
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs(), ex);
            }
            finally
            {
                this.Client = null;
                this.OnDisConnected?.Invoke(this, new SessionEventArgs());
            }
        }

        /// <summary>
        /// 断开与服务端的连接，并释放所有资源（安全的，不会抛出异常）。
        /// </summary>
        public void Dispose()
        {
            this.Close();
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="message"></param>
        public void Send(string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.Send(datas);
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="datas"></param>
        public void Send(byte[] datas)
        {
            this.Adapter.Send(this.Client, datas, false, null);
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="message"></param>
        public void SendAsync(string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.SendAsync(datas);
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="datas"></param>
        public void SendAsync(byte[] datas)
        {
            this.Adapter.Send(this.Client, datas, true, this.SendCallback);
        }
        #endregion
    }
}
